// This source file is part of the polarphp.org open source project
//
// Copyright (c) 2017 - 2018 polarphp software foundation
// Copyright (c) 2017 - 2018 zzu_softboy <zzu_softboy@163.com>
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://polarphp.org/LICENSE.txt for license information
// See https://polarphp.org/CONTRIBUTORS.txt for the list of polarphp project authors
//
// Created by polarboy on 2018/07/07.

#include "polarphp/basic/adt/AllocatorList.h"
#include "polarphp/basic/adt/StlExtras.h"
#include "gtest/gtest.h"

using namespace polar::basic;

namespace {

struct CountsDestructors
{
   static unsigned NumCalls;
   ~CountsDestructors() { ++NumCalls; }
};

unsigned CountsDestructors::NumCalls = 0;

struct MoveOnly
{
   int V;
   explicit MoveOnly(int V) : V(V) {}
   MoveOnly() = delete;
   MoveOnly(MoveOnly &&X) { V = X.V; }
   MoveOnly(const MoveOnly &X) = delete;
   MoveOnly &operator=(MoveOnly &&X) = delete;
   MoveOnly &operator=(const MoveOnly &X) = delete;
};

struct EmplaceOnly
{
   int V1, V2;
   explicit EmplaceOnly(int V1, int V2) : V1(V1), V2(V2) {}
   EmplaceOnly() = delete;
   EmplaceOnly(EmplaceOnly &&X) = delete;
   EmplaceOnly(const EmplaceOnly &X) = delete;
   EmplaceOnly &operator=(EmplaceOnly &&X) = delete;
   EmplaceOnly &operator=(const EmplaceOnly &X) = delete;
};

TEST(BumpPtrListTest, testDefaultConstructor)
{
   BumpPtrList<int> L;
   EXPECT_TRUE(L.empty());
}

TEST(BumpPtrListTest, testPushPopBack)
{
   // Build a list with push_back.
   BumpPtrList<int> L;
   int Ns[] = {1, 3, 9, 5, 7};
   for (const int N : Ns) {
      L.push_back(N);
   }
   // Use iterators to check contents.
   auto I = L.begin();
   for (int N : Ns) {
      EXPECT_EQ(N, *I++);
   }
   EXPECT_EQ(I, L.end());
   // Unbuild the list with pop_back.
   for (int N : reverse(Ns)) {
      EXPECT_EQ(N, L.back());
      L.pop_back();
   }
   EXPECT_TRUE(L.empty());
}

TEST(BumpPtrListTest, testPushPopFront)
{
   // Build a list with push_front.
   BumpPtrList<int> L;
   int Ns[] = {1, 3, 9, 5, 7};
   for (const int N : Ns) {
      L.push_front(N);
   }
   // Use reverse iterators to check contents.
   auto I = L.rbegin();
   for (int N : Ns) {
      EXPECT_EQ(N, *I++);
   }
   EXPECT_EQ(I, L.rend());

   // Unbuild the list with pop_front.
   for (int N : reverse(Ns)) {
      EXPECT_EQ(N, L.front());
      L.pop_front();
   }
   EXPECT_TRUE(L.empty());
}

TEST(BumpPtrListTest, testPushBackMoveOnly)
{
   BumpPtrList<MoveOnly> L;
   int Ns[] = {1, 3, 9, 5, 7};
   for (const int N : Ns) {
      L.push_back(MoveOnly(N));
      EXPECT_EQ(N, L.back().V);
   }
   // Instantiate with MoveOnly.
   while (!L.empty()) {
      L.pop_back();
   }
}

TEST(BumpPtrListTest, testPushFrontMoveOnly)
{
   BumpPtrList<MoveOnly> L;
   int Ns[] = {1, 3, 9, 5, 7};
   for (const int N : Ns) {
      L.push_front(MoveOnly(N));
      EXPECT_EQ(N, L.front().V);
   }
   // Instantiate with MoveOnly.
   while (!L.empty()) {
      L.pop_front();
   }
}

TEST(BumpPtrListTest, testEmplaceBack)
{
   BumpPtrList<EmplaceOnly> L;
   int N1s[] = {1, 3, 9, 5, 7};
   int N2s[] = {7, 3, 1, 8, 2};
   for (int I = 0; I != 5; ++I) {
      L.emplace_back(N1s[I], N2s[I]);
      EXPECT_EQ(N1s[I], L.back().V1);
      EXPECT_EQ(N2s[I], L.back().V2);
   }
   // Instantiate with EmplaceOnly.
   while (!L.empty()) {
      L.pop_back();
   }
}

TEST(BumpPtrListTest, testEmplaceFront)
{
   BumpPtrList<EmplaceOnly> L;
   int N1s[] = {1, 3, 9, 5, 7};
   int N2s[] = {7, 3, 1, 8, 2};
   for (int I = 0; I != 5; ++I) {
      L.emplace_front(N1s[I], N2s[I]);
      EXPECT_EQ(N1s[I], L.front().V1);
      EXPECT_EQ(N2s[I], L.front().V2);
   }
   // Instantiate with EmplaceOnly.
   while (!L.empty()) {
      L.pop_front();
   }
}

TEST(BumpPtrListTest, testSwap)
{
   // Build two lists with different lifetimes and swap them.
   int N1s[] = {1, 3, 5, 7, 9};
   int N2s[] = {2, 4, 6, 8, 10};

   BumpPtrList<int> L1;
   L1.insert(L1.end(), std::begin(N1s), std::end(N1s));
   {
      BumpPtrList<int> L2;
      L2.insert(L2.end(), std::begin(N2s), std::end(N2s));

      // Swap the lists.
      L1.swap(L2);

      // Check L2's contents before it goes out of scope.
      auto I = L2.begin();
      for (int N : N1s) {
         EXPECT_EQ(N, *I++);
      }
      EXPECT_EQ(I, L2.end());
   }
   // Check L1's contents now that L2 is out of scope (with its allocation
   // blocks).
   auto I = L1.begin();
   for (int N : N2s) {
      EXPECT_EQ(N, *I++);
   }
   EXPECT_EQ(I, L1.end());
}

TEST(BumpPtrListTest, testClear)
{
   CountsDestructors::NumCalls = 0;
   CountsDestructors N;
   BumpPtrList<CountsDestructors> L;
   L.push_back(N);
   L.push_back(N);
   L.push_back(N);
   EXPECT_EQ(3u, L.size());
   EXPECT_EQ(0u, CountsDestructors::NumCalls);
   L.pop_back();
   EXPECT_EQ(1u, CountsDestructors::NumCalls);
   L.clear();
   EXPECT_EQ(3u, CountsDestructors::NumCalls);
}

TEST(BumpPtrListTest, testMove)
{
   BumpPtrList<int> L1, L2;
   L1.push_back(1);
   L2.push_back(2);
   L1 = std::move(L2);
   EXPECT_EQ(1u, L1.size());
   EXPECT_EQ(2, L1.front());
   EXPECT_EQ(0u, L2.size());
}

TEST(BumpPtrListTest, testMoveCallsDestructors)
{
   CountsDestructors::NumCalls = 0;
   BumpPtrList<CountsDestructors> L1, L2;
   L1.emplace_back();
   EXPECT_EQ(0u, CountsDestructors::NumCalls);
   L1 = std::move(L2);
   EXPECT_EQ(1u, CountsDestructors::NumCalls);
}

TEST(BumpPtrListTest, testCopy)
{
   BumpPtrList<int> L1, L2;
   L1.push_back(1);
   L2.push_back(2);
   L1 = L2;
   EXPECT_EQ(1u, L1.size());
   EXPECT_EQ(2, L1.front());
   EXPECT_EQ(1u, L2.size());
   EXPECT_EQ(2, L2.front());
}

TEST(BumpPtrListTest, testCopyCallsDestructors)
{
   CountsDestructors::NumCalls = 0;
   BumpPtrList<CountsDestructors> L1, L2;
   L1.emplace_back();
   EXPECT_EQ(0u, CountsDestructors::NumCalls);
   L1 = L2;
   EXPECT_EQ(1u, CountsDestructors::NumCalls);
}

TEST(BumpPtrListTest, testResetAlloc)
{
   // Resetting an empty list should work.
   BumpPtrList<int> L;

   // Resetting an empty list that has allocated should also work.
   L.resetAlloc();
   L.push_back(5);
   L.erase(L.begin());
   L.resetAlloc();

   // Resetting a non-empty list should crash.
   L.push_back(5);
#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
   EXPECT_DEATH(L.resetAlloc(), "Cannot reset allocator if not empty");
#endif
}

} // anonymous namespace
